// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/signin/dice_web_signin_interceptor.h"

#include <optional>
#include <string>

#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/i18n/case_conversion.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/new_tab_page/chrome_colors/generated_colors_info.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/signin/chrome_signin_pref_names.h"
#include "chrome/browser/signin/dice_intercepted_session_startup_helper.h"
#include "chrome/browser/signin/dice_signed_in_profile_creator.h"
#include "chrome/browser/signin/dice_web_signin_interceptor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/signin/web_signin_interceptor.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
#include "chrome/browser/ui/profiles/profile_colors_util.h"
#include "chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/themes/autogenerated_theme_util.h"
#include "components/password_manager/core/browser/password_manager.h"
#include "components/password_manager/core/common/password_manager_ui.h"
#include "components/policy/core/browser/signin/profile_separation_policies.h"
#include "components/policy/core/browser/signin/user_cloud_signin_restriction_policy_fetcher.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#include "components/policy/core/common/policy_utils.h"
#include "components/policy/policy_constants.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search_engines/search_engine_choice/search_engine_choice_utils.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_prefs.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_capabilities.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "components/signin/public/identity_manager/accounts_mutator.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/signin/public/identity_manager/tribool.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/themes.mojom.h"

namespace {

constexpr char kChromeSingInInterceptionSupervisionStateHistogramPrefix[] =
    "Signin.Intercept.Heuristic.SupervisionState";

constexpr size_t kMaxChromeSigninInterceptionDismissCount = 5;

// The user will only see the Chrome Signin bubble reprompt a maximum of 4 times
// (not including the initial time the bubble was declined).
static constexpr int kMaxChromeSigninBubbleRepromptCountAllowed = 4;
// The Chrome Signin bubble can be reprompted only if a minimum duration time
// has passed since the last bubble was shown (either initial bubble or a
// reprompt).
static constexpr base::TimeDelta kMinimumDurationForChromeSigninBubbleReprompt =
    base::Days(60);

// Helper function to return the primary account info. The returned info is
// empty if there is no primary account, and non-empty otherwise. Extended
// fields may be missing if they are not available.
AccountInfo GetPrimaryAccountInfo(signin::IdentityManager* manager) {
  CoreAccountInfo primary_core_account_info =
      manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
  if (primary_core_account_info.IsEmpty()) {
    return AccountInfo();
  }

  AccountInfo primary_account_info =
      manager->FindExtendedAccountInfo(primary_core_account_info);

  if (!primary_account_info.IsEmpty()) {
    return primary_account_info;
  }

  // Return an AccountInfo without extended fields, based on the core info.
  AccountInfo account_info;
  account_info.gaia = primary_core_account_info.gaia;
  account_info.email = primary_core_account_info.email;
  account_info.account_id = primary_core_account_info.account_id;
  return account_info;
}

bool IsFirstAccount(signin::IdentityManager* manager,
                    const std::string& email) {
  std::vector<CoreAccountInfo> accounts_in_chrome =
      manager->GetAccountsWithRefreshTokens();
  // There is not guarantee that the added email/account have a refresh token
  // yet.
  // So we either check that no account exists, or that the only account that
  // has a refresh token is the account we are interested in, to make sure it is
  // the first account.
  return accounts_in_chrome.size() == 0 ||
         (accounts_in_chrome.size() == 1 &&
          gaia::AreEmailsSame(email, accounts_in_chrome[0].email));
}

// Returns the time elapsed since the last Chrome Signin Bubble decline. It is
// expected that a first decline was made and that the setting was not manually
// overridden by the user to do not sign in automatically. In case the
// conditions are not met to retrieve a proper delta time, a default value of 0
// is returned.
base::TimeDelta GetTimeSinceLastChromeSigninDecline(
    const SigninPrefs& signin_prefs,
    const std::string& gaia_id) {
  std::optional<base::Time> last_bubble_decline_time =
      signin_prefs.GetChromeSigninInterceptionLastBubbleDeclineTime(gaia_id);
  // If the value does not exist, this means that the user is either not in the
  // `ChromeSigninUserChoice::kDoNotSignin` or they explicitly changed the
  // setting from the settings page.
  if (!last_bubble_decline_time.has_value()) {
    return base::TimeDelta();
  }

  return base::Time::Now() - last_bubble_decline_time.value();
}

// When a user declines the bubble, potential reprompts are allowed in future
// based on the time that passes since the bubble decline and the last
// reprompts, with a fixed amount of allowed reprompts per account.
// Reprompts are not allowed if the user explicitly set the Chrome Signin
// setting to do not signin.
bool ShouldAllowChromeSigninBubbleReprompt(const SigninPrefs& signin_prefs,
                                           const std::string& gaia_id) {
  // Reprompts are only allowed if the user choice is to not sign in
  // automatically.
  if (signin_prefs.GetChromeSigninInterceptionUserChoice(gaia_id) !=
      ChromeSigninUserChoice::kDoNotSignin) {
    return false;
  }

  // Maximum reprompt count check.
  int reprompt_count = signin_prefs.GetChromeSigninBubbleRepromptCount(gaia_id);
  if (reprompt_count >= kMaxChromeSigninBubbleRepromptCountAllowed) {
    return false;
  }

  // If the user has set the setting manually, this value will be 0, making sure
  // it does not satisfy the minimum requirement for repromts.
  base::TimeDelta time_since_last_decline =
      GetTimeSinceLastChromeSigninDecline(signin_prefs, gaia_id);

  // Minimum duration since last chrome signin decline check.
  return time_since_last_decline >=
         kMinimumDurationForChromeSigninBubbleReprompt;
}

// Set showing the bubble as a reprompt if the user previously declined the
// bubble and did not override the choice in the settings page (having a
// declined choice time).
void MaybeUpdateRepromptInfoAfterDecline(SigninPrefs& signin_prefs,
                                         const std::string& gaia_id) {
  // Check if this is was a reprompt.
  if (!ShouldAllowChromeSigninBubbleReprompt(signin_prefs, gaia_id)) {
    return;
  }

  // Record this value before updating the new decline time.
  // There is no need to record values with less than the minimum duration, and
  // recording max values with up to 1 year (365 days). There is no need for
  // more granularity.
  base::UmaHistogramCustomCounts(
      "Signin.Intercept.ChromeSignin.NumberOfDaysSinceLastDecline",
      GetTimeSinceLastChromeSigninDecline(signin_prefs, gaia_id).InDays(),
      /*min=*/kMinimumDurationForChromeSigninBubbleReprompt.InDays(),
      /*exclusive_max=*/365 /*days*/,
      /*buckets=*/50);

  signin_prefs.SetChromeSigninInterceptionLastBubbleDeclineTime(
      gaia_id, base::Time::Now());
  int new_reprompt_count =
      signin_prefs.IncrementChromeSigninBubbleRepromptCount(gaia_id);

  base::UmaHistogramExactLinear("Signin.Intercept.ChromeSignin.RepromptCount",
                                new_reprompt_count,
                                kMaxChromeSigninBubbleRepromptCountAllowed + 1);
}

// Returns `ShouldShowChromeSigninBubbleWithReason::kShouldShow` if sign in
// happens through the web and Chrome isn't already signed in.
ShouldShowChromeSigninBubbleWithReason MaybeShouldShowChromeSigninBubble(
    PrefService& pref_service,
    signin::IdentityManager* manager,
    const std::string& gaia_id,
    signin_metrics::AccessPoint access_point) {
  // If the access point is not set, we cannot accurately know if we have to
  // show the bubble or not, so we will not show it.
  if (access_point == signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN) {
    return ShouldShowChromeSigninBubbleWithReason::
        kShouldNotShowUnknownAccessPoint;
  }

  // Only show the Chrome Signin Bubble when the signin event occurred through
  // a regular web signin in (not triggered through a chrome feature).
  if (access_point != signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN) {
    return ShouldShowChromeSigninBubbleWithReason::
        kShouldNotShowNotFromWebSignin;
  }

  // Check if an account is already signed in to Chrome.
  //
  // If explicit browser signin is disabled, we ignore this condition since the
  // primary account will be set prior to this call. This is done for metric
  // purposes, this is safe since the bubble will not be shown in that case any
  // way.
  if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
      manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
    return ShouldShowChromeSigninBubbleWithReason::
        kShouldNotShowAlreadySignedIn;
  }

  // Check for the Chrome Signin setting value and possible reprompts.
  if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
    SigninPrefs signin_prefs(pref_service);
    ChromeSigninUserChoice user_choice =
        signin_prefs.GetChromeSigninInterceptionUserChoice(gaia_id);
    switch (user_choice) {
      case ChromeSigninUserChoice::kNoChoice:
      case ChromeSigninUserChoice::kAlwaysAsk:
        break;
      case ChromeSigninUserChoice::kSignin:
        // This should not happen in a regular case, but rather an edge case; if
        // the user changed their preference while the interception is in
        // progress. Might also happen during tests that do not test the full
        // flow; mainly the early flow that automatically signs in and do not
        // get to this point.
        return ShouldShowChromeSigninBubbleWithReason::kShouldNotShowUserChoice;
      case ChromeSigninUserChoice::kDoNotSignin:
        if (!ShouldAllowChromeSigninBubbleReprompt(signin_prefs, gaia_id)) {
          return ShouldShowChromeSigninBubbleWithReason::
              kShouldNotShowUserChoice;
        }
        break;
    }
  }

  return ShouldShowChromeSigninBubbleWithReason::kShouldShow;
}

// Returns true if we have the minimum extended account information needed to
// make a best-effort intercept heuristic decision. If we fail to retrieve
// this information we will cancel the interception completely.
// Returns false otherwise.
bool IsRequiredExtendedAccountInfoAvailable(const AccountInfo& account_info) {
  return account_info.IsValid();
}

// Returns true if enterprise separation is required.
// Returns false is enterprise separation is not required.
// Returns no value if info is required to determine if enterprise separation
// is required. If `profile_separation_policies` is `std::nullopt` then the
// user cloud profile separation policies have not yet been fetched.
std::optional<bool> EnterpriseSeparationMaybeRequired(
    Profile* profile,
    signin::IdentityManager* identity_manager,
    const std::string& email,
    bool is_new_account_interception,
    const std::optional<policy::ProfileSeparationPolicies>&
        intercepted_profile_separation_policies,
    bool expects_intercepted_profile_separation_policies_for_testing) {
  CoreAccountInfo primary_core_account_info =
      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);

  // Enforce separation for new accounts or re-auth of existing secondary
  // accounts.
  if ((is_new_account_interception ||
       !gaia::AreEmailsSame(primary_core_account_info.email, email)) &&
      !signin_util::IsAccountExemptedFromEnterpriseProfileSeparation(profile,
                                                                     email)) {
    return true;
  }

  // No enterprise separation required for consumer accounts.
  if (!signin::AccountManagedStatusFinder::MayBeEnterpriseUserBasedOnEmail(
          email)) {
    return false;
  }

  auto intercepted_account_info =
      identity_manager->FindExtendedAccountInfoByEmailAddress(email);
  // If the account info is not found, we need to wait for the info to be
  // available.
  if (!IsRequiredExtendedAccountInfoAvailable(intercepted_account_info)) {
    return std::nullopt;
  }
  // If the intercepted account is not managed, no interception required.
  if (!intercepted_account_info.IsManaged()) {
    return false;
  }
  // If `profile` requires enterprise profile separation, return true.
  // Here we only check the legacy policy by passing an empty email since the
  // new ProfileSeparationSetting policy is checked early in the function.
  if (signin_util::IsProfileSeparationEnforcedByProfile(
          profile,
          /*intercepted_account_email=*/std::string())) {
    return true;
  }

  if (signin_util::IsProfileSeparationEnforcedByPolicies(
          intercepted_profile_separation_policies.value_or(
              policy::ProfileSeparationPolicies()))) {
    return true;
  }

  // If we still do not know if profile separation is required, the account
  // level policies for the intercepted account must be fetched if possible.
  // If `g_browser_process->system_network_context_manager()` is  equal to
  // nullptr, we are probably in tests and should not try to fetch any policies.
  // If `expects_intercepted_profile_separation_policies_for_testing`, even if
  // we are in tests, we have a value set locally that will not require us to
  // make a network call.
  // Fetching the value will not be possible isf we cannot make network calls
  // nor have a value set locally for testing.
  if (!intercepted_profile_separation_policies.has_value() &&
      (g_browser_process->system_network_context_manager() ||
       expects_intercepted_profile_separation_policies_for_testing)) {
    return std::nullopt;
  }

  return false;
}

void RecordShouldShowChromeSigninBubbleReason(
    ShouldShowChromeSigninBubbleWithReason reason) {
  // This metric will be recorded both when
  // `switches::kExplicitBrowserSigninUIOnDesktop` is enabled and disabled when
  // the Chrome Signin bubble is expected to be shown or not.
  base::UmaHistogramEnumeration(
      "Signin.Intercept.Heuristic.ShouldShowChromeSigninBubbleWithReason",
      reason);
}

bool IsPrimaryAccountInterception(const CoreAccountId& account_id,
                                  signin::IdentityManager* identity_manager) {
  return account_id ==
         identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
}

bool IsReauthPrimaryAccount(bool new_account_interception,
                            const CoreAccountId& account_id,
                            signin::IdentityManager* identity_manager) {
  return !new_account_interception &&
         IsPrimaryAccountInterception(account_id, identity_manager);
}

// Returns true if the current state is inconsistent, which happens by
// signing into the web with an account B while being in sign in pending state
// with account A. Returns false otherwise. Used for metrics.
bool IsInInconsistentStateWithPrimaryAccount(
    const CoreAccountId& account_id,
    signin::IdentityManager* identity_manager) {
  return signin_util::IsSigninPending(identity_manager) &&
         identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
                 .account_id != account_id;
}

SinginInterceptSupervisionState CapabilityToSupervisionState(
    const AccountCapabilities& capabilities) {
  const signin::Tribool is_supervised =
      capabilities.is_subject_to_parental_controls();
  switch (is_supervised) {
    case (signin::Tribool::kTrue):
      return SinginInterceptSupervisionState::kSupervisedUser;
    case (signin::Tribool::kFalse):
      return SinginInterceptSupervisionState::kRegularUser;
    case (signin::Tribool::kUnknown): {
      return SinginInterceptSupervisionState::kUnknownSupervision;
    }
  }
  NOTREACHED();
}

void MaybeRecordSupervisedUserStateMetrics(
    const AccountInfo& intercepted_account_info,
    WebSigninInterceptor::SigninInterceptionType interception_type) {
  if (interception_type !=
          WebSigninInterceptor::SigninInterceptionType::kChromeSignin &&
      interception_type !=
          WebSigninInterceptor::SigninInterceptionType::kMultiUser &&
      interception_type !=
          WebSigninInterceptor::SigninInterceptionType::kProfileSwitch) {
    return;
  }

  base::UmaHistogramEnumeration(
      kChromeSingInInterceptionSupervisionStateHistogramPrefix +
          DiceWebSigninInterceptorDelegate::GetHistogramSuffix(
              interception_type),
      CapabilityToSupervisionState(intercepted_account_info.capabilities));
}

}  // namespace

DiceWebSigninInterceptor::DiceWebSigninInterceptor(
    Profile* profile,
    std::unique_ptr<WebSigninInterceptor::Delegate> delegate)
    : profile_(profile),
      identity_manager_(IdentityManagerFactory::GetForProfile(profile)),
      delegate_(std::move(delegate)),
      state_(std::make_unique<ResetableState>()) {
  DCHECK(profile_);
  DCHECK(identity_manager_);
  DCHECK(delegate_);
}

DiceWebSigninInterceptor::~DiceWebSigninInterceptor() = default;

DiceWebSigninInterceptor::ResetableState::ResetableState() = default;
DiceWebSigninInterceptor::ResetableState::~ResetableState() = default;

DiceWebSigninInterceptor::ProfilePresets::ProfilePresets(SkColor profile_color)
    : profile_color(profile_color) {}

DiceWebSigninInterceptor::ProfilePresets::~ProfilePresets() = default;

// static
void DiceWebSigninInterceptor::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterDictionaryPref(prefs::kProfileCreationInterceptionDeclined);
  registry->RegisterBooleanPref(prefs::kSigninInterceptionEnabled, true);
  registry->RegisterStringPref(prefs::kManagedAccountsSigninRestriction,
                               std::string());
  registry->RegisterStringPref(prefs::kSigninInterceptionIDPCookiesUrl,
                               std::string());
  registry->RegisterBooleanPref(
      prefs::kManagedAccountsSigninRestrictionScopeMachine, false);
  registry->RegisterIntegerPref(prefs::kProfileSeparationSettings, 0);
  registry->RegisterIntegerPref(prefs::kProfileSeparationDataMigrationSettings,
                                1);
  registry->RegisterListPref(prefs::kProfileSeparationDomainExceptionList);
  registry->RegisterStringPref(
      prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage, std::string());
}

std::optional<SigninInterceptionHeuristicOutcome>
DiceWebSigninInterceptor::GetHeuristicOutcome(
    bool is_new_account,
    bool is_sync_signin,
    const std::string& email,
    const std::string& gaia_id,
    bool update_state,
    const ProfileAttributesEntry** entry) const {
  bool signin_interception_enabled =
      profile_->GetPrefs()->GetBoolean(prefs::kSigninInterceptionEnabled);

  if (is_sync_signin) {
    // Do not intercept signins from the Sync startup flow.
    // Note: |is_sync_signin| is an approximation, and in rare cases it may be
    // true when in fact the signin was not a sync signin. In this case the
    // interception is missed.
    return SigninInterceptionHeuristicOutcome::kAbortSyncSignin;
  }

  auto enforce_enterprise_separation = EnterpriseSeparationMaybeRequired(
      profile_, identity_manager_, email, is_new_account,
      /*intercepted_profile_separation_policies=*/std::nullopt,
      /*expects_intercepted_profile_separation_policies_for_testing=*/
      intercepted_account_profile_separation_policies_response_for_testing_
          .has_value());

  // If we do not have all the information to enforce or not enterprise profile
  // separation, return `std::nullopt` so that we can try and get more info on
  // the intercepted account.
  if (!enforce_enterprise_separation.has_value()) {
    return std::nullopt;
  }

  const ProfileAttributesEntry* switch_to_entry = ShouldShowProfileSwitchBubble(
      email,
      &g_browser_process->profile_manager()->GetProfileAttributesStorage());
  if (switch_to_entry && entry) {
    *entry = switch_to_entry;
  }

  if (enforce_enterprise_separation.value()) {
    return switch_to_entry
               ? SigninInterceptionHeuristicOutcome::
                     kInterceptEnterpriseForcedProfileSwitch
               : SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced;
  }

  CHECK(!enforce_enterprise_separation.value());

  // If interception is disabled and there are no enforce enterprise
  // profile separation policies abort.
  if (!signin_interception_enabled) {
    return SigninInterceptionHeuristicOutcome::kAbortInterceptionDisabled;
  }

  // Don't show profile switch for reauth.
  if (switch_to_entry && is_new_account) {
    return SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch;
  }

  // The `gaia_id` must have a value to properly read the prefs related to the
  // account and the Chrome Signin Bubble.
  if (!gaia_id.empty()) {
    // Chrome sign in bubble is shown if chrome isn't signed in.
    ShouldShowChromeSigninBubbleWithReason should_show_chrome_signin_bubble =
        MaybeShouldShowChromeSigninBubble(*profile_->GetPrefs(),
                                          identity_manager_, gaia_id,
                                          state_->access_point_);
    if (update_state) {
      state_->should_show_chrome_signin_bubble_ =
          should_show_chrome_signin_bubble;
    }

    // Showing the Chrome Signin Bubble is part of the Uno Desktop project.
    if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
        should_show_chrome_signin_bubble ==
            ShouldShowChromeSigninBubbleWithReason::kShouldShow) {
      return SigninInterceptionHeuristicOutcome::kInterceptChromeSignin;
    }
  }

  // Do not intercept reauth.
  // Reauth is subject to chrome sign in bubble which is checked earlier in this
  // function.
  if (!is_new_account) {
    return SigninInterceptionHeuristicOutcome::kAbortAccountNotNew;
  }

  // From this point the remaining possible interceptions involve creating a new
  // profile.
  if (!profiles::IsProfileCreationAllowed()) {
    return SigninInterceptionHeuristicOutcome::kAbortProfileCreationDisallowed;
  }

  if (IsFirstAccount(identity_manager_, email)) {
    // Enterprise and multi-user bubbles are only shown if there are multiple
    // accounts. The intercepted account may not be added to chrome yet.
    return SigninInterceptionHeuristicOutcome::kAbortSingleAccount;
  }

  if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
    // This is not the first account in the identity manager but there is no
    // primary account, all the accounts are in the UNO web-only state, so do
    // not intercept.
    DCHECK(switches::IsExplicitBrowserSigninUIOnDesktopEnabled());
    return SigninInterceptionHeuristicOutcome::
        kAbortNotFirstAccountButNoPrimaryAccount;
  }

  if (HasUserDeclinedProfileCreation(email)) {
    return SigninInterceptionHeuristicOutcome::
        kAbortUserDeclinedProfileForAccount;
  }

  return std::nullopt;
}

void DiceWebSigninInterceptor::MaybeInterceptWebSignin(
    content::WebContents* web_contents,
    CoreAccountId account_id,
    signin_metrics::AccessPoint access_point,
    bool is_new_account,
    bool is_sync_signin) {
  // If the user is in sign in pending state and signs in with a different
  // account, it means that they enter an inconsistent state. Record this event
  // so that we can check afterwards if this state is resolved by accepting the
  // ProfileSwitchBubble or MultiUserBubble.
  if (IsInInconsistentStateWithPrimaryAccount(account_id, identity_manager_)) {
    base::UmaHistogramBoolean("Signin.SigninPending.InconsistentStateInvoked",
                              true);
  }

  if (state_->is_interception_in_progress_) {
    // Multiple concurrent interceptions are not supported.
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortInterceptInProgress);
    return;
  }
  DCHECK_EQ(state_->interception_start_time_, base::TimeTicks());
  state_->interception_start_time_ = base::TimeTicks::Now();
  state_->access_point_ = access_point;

  if (!web_contents) {
    // The tab has been closed (typically during the token exchange, which may
    // take some time).
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortTabClosed);
    Reset();
    return;
  }

  if (!delegate_->IsSigninInterceptionSupported(*web_contents)) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortNoSupportedBrowser);
    Reset();
    return;
  }

  // Do not show the interception UI if a password update is required: both
  // bubbles cannot be shown at the same time and the password update is more
  // important.
  ChromePasswordManagerClient* password_manager_client =
      ChromePasswordManagerClient::FromWebContents(web_contents);
  if (password_manager_client && password_manager_client->GetPasswordManager()
                                     ->IsFormManagerPendingPasswordUpdate()) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortPasswordUpdatePending);
    Reset();
    return;
  }

  ManagePasswordsUIController* password_controller =
      ManagePasswordsUIController::FromWebContents(web_contents);
  if (password_controller &&
      password_controller->GetState() ==
          password_manager::ui::State::PENDING_PASSWORD_UPDATE_STATE) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortPasswordUpdate);
    Reset();
    return;
  }

  AccountInfo account_info =
      identity_manager_->FindExtendedAccountInfoByAccountId(account_id);
  DCHECK(!account_info.IsEmpty()) << "Intercepting unknown account.";
  const ProfileAttributesEntry* entry = nullptr;
  std::optional<SigninInterceptionHeuristicOutcome> heuristic_outcome =
      GetHeuristicOutcome(is_new_account, is_sync_signin, account_info.email,
                          account_info.gaia,
                          /*update_state=*/true, &entry);
  state_->account_id_ = account_id;
  state_->is_interception_in_progress_ = true;
  state_->new_account_interception_ = is_new_account;
  state_->web_contents_ = web_contents->GetWeakPtr();

  if (heuristic_outcome &&
      !SigninInterceptionHeuristicOutcomeIsSuccess(*heuristic_outcome)) {
    RecordSigninInterceptionHeuristicOutcome(*heuristic_outcome);
    if (state_->should_show_chrome_signin_bubble_) {
      RecordShouldShowChromeSigninBubbleReason(
          state_->should_show_chrome_signin_bubble_.value());
    }
    Reset();
    return;
  }

  // Start a timeout for the async operations we're kicking off.
  state_->interception_info_available_timeout_.Reset(
      base::BindOnce(&DiceWebSigninInterceptor::OnInterceptionInfoFetchTimeout,
                     base::Unretained(this)));
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, state_->interception_info_available_timeout_.callback(),
      base::Seconds(5));

  // Process the interception (maybe kicking off async fetches).
  ProcessInterceptionOrWait(account_info, /*timed_out=*/false);
}

void DiceWebSigninInterceptor::CreateBrowserAfterSigninInterception(
    CoreAccountId account_id,
    content::WebContents* intercepted_contents,
    std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle> bubble_handle,
    bool is_new_profile,
    WebSigninInterceptor::SigninInterceptionType interception_type) {
  DCHECK(!state_->session_startup_helper_);
  DCHECK(bubble_handle);
  state_->interception_bubble_handle_ = std::move(bubble_handle);
  state_->account_id_ = account_id;
  state_->interception_type_ = interception_type;
  state_->session_startup_helper_ =
      std::make_unique<DiceInterceptedSessionStartupHelper>(
          profile_, is_new_profile, account_id, intercepted_contents);
  state_->session_startup_helper_->Startup(
      base::BindOnce(&DiceWebSigninInterceptor::OnNewBrowserCreated,
                     base::Unretained(this), is_new_profile));
}

void DiceWebSigninInterceptor::Shutdown() {
  if (state_->is_interception_in_progress_ &&
      !state_->was_interception_ui_displayed_) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortShutdown);
  }
  Reset();
}

void DiceWebSigninInterceptor::Reset() {
  state_ = std::make_unique<ResetableState>();
  account_info_update_observation_.Reset();
}

const ProfileAttributesEntry*
DiceWebSigninInterceptor::ShouldShowProfileSwitchBubble(
    const std::string& intercepted_email,
    ProfileAttributesStorage* profile_attribute_storage) const {
  // Check if there is already an existing profile with this account.
  base::FilePath profile_path = profile_->GetPath();
  for (const auto* entry :
       profile_attribute_storage->GetAllProfilesAttributes()) {
    if (entry->GetPath() == profile_path) {
      continue;
    }
    if (gaia::AreEmailsSame(intercepted_email,
                            base::UTF16ToUTF8(entry->GetUserName()))) {
      return entry;
    }
  }
  return nullptr;
}

bool DiceWebSigninInterceptor::ShouldEnforceEnterpriseProfileSeparation(
    const AccountInfo& intercepted_account_info) const {
  DCHECK(IsRequiredExtendedAccountInfoAvailable(intercepted_account_info));
  CoreAccountInfo primary_account =
      identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
  // In case of re-auth of a managed primary account, do not show the enterprise
  // separation dialog if the user already consented to enterprise management.
  if (intercepted_account_info.IsManaged() &&
      IsReauthPrimaryAccount(state_->new_account_interception_,
                             intercepted_account_info.account_id,
                             identity_manager_)) {
    // Sync users are considered to have implicitly accepted management.
    // Returns true only for reauth for primary accounts without sync where
    // management hasn't been accepted.
    return !identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync) &&
           !enterprise_util::UserAcceptedAccountManagement(profile_);
  }

  if (!signin_util::IsAccountExemptedFromEnterpriseProfileSeparation(
          profile_, intercepted_account_info.email)) {
    return true;
  }

  if (!signin_util::IsProfileSeparationEnforcedByProfile(
          profile_, intercepted_account_info.email) &&
      !signin_util::IsProfileSeparationEnforcedByPolicies(
          state_->intercepted_account_profile_separation_policies_.value_or(
              policy::ProfileSeparationPolicies()))) {
    return false;
  }

  return state_->new_account_interception_ &&
         intercepted_account_info.IsManaged();
}

bool DiceWebSigninInterceptor::ShouldShowEnterpriseDialog(
    const AccountInfo& intercepted_account_info) const {
  DCHECK(IsRequiredExtendedAccountInfoAvailable(intercepted_account_info));

  if (!base::FeatureList::IsEnabled(
          features::kEnterpriseUpdatedProfileCreationScreen)) {
    return false;
  }

  if (state_->intercepted_account_profile_separation_policies_
          .value_or(policy::ProfileSeparationPolicies())
          .profile_separation_settings()
          .value_or(policy::ProfileSeparationSettings::SUGGESTED) !=
      policy::ProfileSeparationSettings::SUGGESTED) {
    return false;
  }

  // Check if the intercepted account is managed and has not yet accepted
  // management.
  if (!intercepted_account_info.IsManaged() ||
      enterprise_util::UserAcceptedAccountManagement(profile_)) {
    return false;
  }

  if (IsPrimaryAccountInterception(intercepted_account_info.account_id,
                                   identity_manager_)) {
    return true;
  }

  return false;
}

bool DiceWebSigninInterceptor::ShouldShowEnterpriseBubble(
    const AccountInfo& intercepted_account_info) const {
  DCHECK(IsRequiredExtendedAccountInfoAvailable(intercepted_account_info));
  // Check if the intercepted account or the primary account is managed.
  AccountInfo primary_acccount = GetPrimaryAccountInfo(identity_manager_);

  if (primary_acccount.IsEmpty() ||
      IsPrimaryAccountInterception(intercepted_account_info.account_id,
                                   identity_manager_)) {
    return false;
  }

  return intercepted_account_info.IsManaged() || primary_acccount.IsManaged();
}

bool DiceWebSigninInterceptor::ShouldShowMultiUserBubble(
    const AccountInfo& intercepted_account_info) const {
  DCHECK(IsRequiredExtendedAccountInfoAvailable(intercepted_account_info));
  if (identity_manager_->GetAccountsWithRefreshTokens().size() <= 1u ||
      !identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
    return false;
  }
  // Check if the account has the same name as another account in the profile.
  for (const auto& account_info :
       identity_manager_->GetExtendedAccountInfoForAccountsWithRefreshToken()) {
    if (account_info.account_id == intercepted_account_info.account_id) {
      continue;
    }
    // Case-insensitve comparison supporting non-ASCII characters.
    if (base::i18n::FoldCase(base::UTF8ToUTF16(account_info.given_name)) ==
        base::i18n::FoldCase(
            base::UTF8ToUTF16(intercepted_account_info.given_name))) {
      return false;
    }
  }
  return true;
}

bool DiceWebSigninInterceptor::ShouldShowChromeSigninBubble(
    const std::string& gaia_id) {
  state_->should_show_chrome_signin_bubble_ = MaybeShouldShowChromeSigninBubble(
      *profile_->GetPrefs(), identity_manager_, gaia_id, state_->access_point_);
  CHECK(state_->should_show_chrome_signin_bubble_.has_value());
  RecordShouldShowChromeSigninBubbleReason(
      state_->should_show_chrome_signin_bubble_.value());

  return switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
         state_->should_show_chrome_signin_bubble_ ==
             ShouldShowChromeSigninBubbleWithReason::kShouldShow;
}

void DiceWebSigninInterceptor::ShowSigninInterceptionBubble(
    const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
    base::OnceCallback<void(SigninInterceptionResult)> callback) {
  state_->was_interception_ui_displayed_ = true;
  state_->interception_type_ = bubble_parameters.interception_type;
  state_->interception_bubble_handle_ = delegate_->ShowSigninInterceptionBubble(
      state_->web_contents_.get(), bubble_parameters, std::move(callback));
}

void DiceWebSigninInterceptor::EnsureObservingExtendedAccountInfo() {
  // Start an observation if one isn't already in progress.
  if (!account_info_update_observation_.IsObserving()) {
    account_info_update_observation_.Observe(identity_manager_.get());
  }
}

void DiceWebSigninInterceptor::ProcessInterceptionOrWait(
    const AccountInfo& info,
    bool timed_out) {
  DCHECK_EQ(info.account_id, state_->account_id_);

  if (!IsRequiredExtendedAccountInfoAvailable(info)) {
    // We can't process the interception with the information currently
    // available.
    //
    // If this is a timeout we abort the interception, otherwise we wait for
    // the remaining information to be fetched asynchronously.
    if (timed_out) {
      RecordSigninInterceptionHeuristicOutcome(
          SigninInterceptionHeuristicOutcome::kAbortAccountInfoTimeout);
      Reset();
      return;
    }
    EnsureObservingExtendedAccountInfo();
    return;
  }

  if (timed_out) {
    // We've timed out waiting for some optional information - process the
    // interception with what we have.
    OnInterceptionReadyToBeProcessed(info);
    return;
  }

  bool have_all_extended_account_info =
      IsFullExtendedAccountInfoAvailable(info);
  bool have_all_enterprise_info =
      EnterpriseSeparationMaybeRequired(
          profile_, identity_manager_, info.email,
          state_->new_account_interception_,
          state_->intercepted_account_profile_separation_policies_,
          /*expects_intercepted_profile_separation_policies_for_testing=*/
          intercepted_account_profile_separation_policies_response_for_testing_
              .has_value())
          .has_value();

  if (!have_all_extended_account_info) {
    // We're need more extended account info - ensure we're waiting on that.
    EnsureObservingExtendedAccountInfo();
  } else {
    account_info_update_observation_.Reset();
  }

  if (!have_all_enterprise_info) {
    // Fetch the ManagedAccountsSigninRestriction policy value for the
    // intercepted account with a timeout.
    EnsureAccountLevelSigninRestrictionFetchInProgress(
        info, base::BindOnce(
                  &DiceWebSigninInterceptor::
                      OnAccountLevelManagedAccountsSigninRestrictionReceived,
                  base::Unretained(this), info));
  }

  if (have_all_extended_account_info && have_all_enterprise_info) {
    // We have all the information we need - process the interception.
    state_->interception_info_available_timeout_.Cancel();
    OnInterceptionReadyToBeProcessed(info);
    return;
  }
}

void DiceWebSigninInterceptor::OnInterceptionReadyToBeProcessed(
    const AccountInfo& info) {
  DCHECK_EQ(info.account_id, state_->account_id_);
  DCHECK(IsRequiredExtendedAccountInfoAvailable(info));

  std::optional<WebSigninInterceptor::SigninInterceptionType> interception_type;

  ProfileAttributesEntry* entry =
      g_browser_process->profile_manager()
          ->GetProfileAttributesStorage()
          .GetProfileAttributesWithPath(profile_->GetPath());
  SkColor profile_color = GenerateNewProfileColor(entry).color;

  const ProfileAttributesEntry* switch_to_entry = ShouldShowProfileSwitchBubble(
      info.email,
      &g_browser_process->profile_manager()->GetProfileAttributesStorage());

  bool force_profile_separation =
      ShouldEnforceEnterpriseProfileSeparation(info);
  bool reauth = !state_->new_account_interception_;
  bool show_link_data_option = false;

  if (force_profile_separation) {
    if (switch_to_entry) {
      interception_type =
          WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced;
      RecordSigninInterceptionHeuristicOutcome(
          SigninInterceptionHeuristicOutcome::
              kInterceptEnterpriseForcedProfileSwitch);
    } else {
      interception_type =
          WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced;
      auto primary_account_id =
          identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
      show_link_data_option =
          (primary_account_id.empty() ||
           primary_account_id == info.account_id) &&
          signin_util::
              ProfileSeparationAllowsKeepingUnmanagedBrowsingDataInManagedProfile(
                  profile_,
                  state_->intercepted_account_profile_separation_policies_
                      .value_or(policy::ProfileSeparationPolicies()));
      RecordSigninInterceptionHeuristicOutcome(
          SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
    }
  } else if (ShouldShowEnterpriseDialog(info)) {
    interception_type = WebSigninInterceptor::SigninInterceptionType::
        kEnterpriseAcceptManagement;
    show_link_data_option = true;
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
  } else if (!profile_->GetPrefs()->GetBoolean(
                 prefs::kSigninInterceptionEnabled)) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortInterceptionDisabled);
    Reset();
    return;
  } else if (!reauth && switch_to_entry) {
    // Propose account switching if we skipped in GetHeuristicOutcome because we
    // returned a nullptr to get more information about forced enterprise
    // profile separation.
    interception_type =
        WebSigninInterceptor::SigninInterceptionType::kProfileSwitch;
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch);
  } else if (ShouldShowChromeSigninBubble(info.gaia)) {
    interception_type =
        WebSigninInterceptor::SigninInterceptionType::kChromeSignin;
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kInterceptChromeSignin);
  } else if (reauth) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortAccountNotNew);
    Reset();
    return;
  } else if (HasUserDeclinedProfileCreation(info.email)) {
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::
            kAbortUserDeclinedProfileForAccount);
    Reset();
    return;
  } else if (ShouldShowEnterpriseBubble(info)) {
    interception_type =
        WebSigninInterceptor::SigninInterceptionType::kEnterprise;
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
  } else if (ShouldShowMultiUserBubble(info)) {
    interception_type =
        WebSigninInterceptor::SigninInterceptionType::kMultiUser;
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kInterceptMultiUser);
  }

  if (!interception_type) {
    // Signin should not be intercepted.
    RecordSigninInterceptionHeuristicOutcome(
        SigninInterceptionHeuristicOutcome::kAbortAccountInfoNotCompatible);
    Reset();
    return;
  }

  bool show_managed_disclaimer =
      *interception_type !=
          WebSigninInterceptor::SigninInterceptionType::kProfileSwitch &&
      (info.IsManaged() ||
       policy::ManagementServiceFactory::GetForPlatform()->IsManaged());

  MaybeRecordSupervisedUserStateMetrics(info, interception_type.value());

  WebSigninInterceptor::Delegate::BubbleParameters bubble_parameters(
      *interception_type, info, GetPrimaryAccountInfo(identity_manager_),
      GetAutogeneratedThemeColors(profile_color).frame_color,
      show_link_data_option, show_managed_disclaimer);

  base::OnceCallback<void(SigninInterceptionResult)> callback;
  switch (*interception_type) {
    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitch:
    case WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced:
      callback = base::BindOnce(
          &DiceWebSigninInterceptor::OnProfileSwitchChoice,
          base::Unretained(this), info.email, switch_to_entry->GetPath());
      break;
    case WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced:
    case WebSigninInterceptor::SigninInterceptionType::
        kEnterpriseAcceptManagement:
      callback = base::BindOnce(
          &DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult,
          base::Unretained(this), info, profile_color);
      break;
    case WebSigninInterceptor::SigninInterceptionType::kEnterprise:
    case WebSigninInterceptor::SigninInterceptionType::kMultiUser:
      callback =
          base::BindOnce(&DiceWebSigninInterceptor::OnProfileCreationChoice,
                         base::Unretained(this), info, profile_color);
      break;
    case WebSigninInterceptor::SigninInterceptionType::kChromeSignin:
      callback = base::BindOnce(&DiceWebSigninInterceptor::OnChromeSigninChoice,
                                base::Unretained(this), info);
      break;
    case WebSigninInterceptor::SigninInterceptionType::kEnterpriseOIDC:
      NOTREACHED() << "This interception type should not happen in DICE";
  }
  ShowSigninInterceptionBubble(bubble_parameters, std::move(callback));
}

void DiceWebSigninInterceptor::OnExtendedAccountInfoUpdated(
    const AccountInfo& info) {
  if (info.account_id != state_->account_id_) {
    return;
  }
  ProcessInterceptionOrWait(info, false);
}

void DiceWebSigninInterceptor::OnExtendedAccountInfoRemoved(
    const AccountInfo& info) {
  if (info.account_id != state_->account_id_) {
    return;
  }
  RecordSigninInterceptionHeuristicOutcome(
      SigninInterceptionHeuristicOutcome::kAbortSignedOut);
  Reset();
}

void DiceWebSigninInterceptor::OnInterceptionInfoFetchTimeout() {
  account_info_update_observation_.Reset();
  if (!state_->intercepted_account_profile_separation_policies_.has_value()) {
    state_->intercepted_account_profile_separation_policies_ =
        policy::ProfileSeparationPolicies();
  }

  AccountInfo account_info =
      identity_manager_->FindExtendedAccountInfoByAccountId(
          state_->account_id_);
  ProcessInterceptionOrWait(account_info, /*timed_out=*/true);
}

void DiceWebSigninInterceptor::OnProfileCreationChoice(
    const AccountInfo& account_info,
    SkColor profile_color,
    SigninInterceptionResult create) {
  if (create != SigninInterceptionResult::kAccepted) {
    if (create == SigninInterceptionResult::kDeclined) {
      IncrementEmailToCountDictionaryPref(
          prefs::kProfileCreationInterceptionDeclined, account_info.email);
    }
    Reset();
    return;
  }

  DCHECK(state_->interception_bubble_handle_);
  std::u16string profile_name =
      profiles::GetDefaultNameForNewSignedInProfile(account_info);
  ProfilePresets profile_presets(profile_color);
  profile_presets.search_engine_choice_data =
      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile_);

  DCHECK(!state_->dice_signed_in_profile_creator_);
  // Unretained is fine because the profile creator is owned by this.
  state_->dice_signed_in_profile_creator_ =
      std::make_unique<DiceSignedInProfileCreator>(
          profile_, state_->account_id_, profile_name,
          profiles::GetPlaceholderAvatarIndex(),
          base::BindOnce(
              &DiceWebSigninInterceptor::OnNewSignedInProfileCreated,
              base::Unretained(this),
              std::optional<ProfilePresets>(std::move(profile_presets))));
}

SigninInterceptionResult
DiceWebSigninInterceptor::ProcessChromeSigninUserChoice(
    SigninInterceptionResult result,
    const std::string& gaia_id) {
  CHECK(switches::IsExplicitBrowserSigninUIOnDesktopEnabled());
  SigninPrefs signin_prefs(*profile_->GetPrefs());
  // When in `ChromeSigninUserChoice::kAlwaysAsk` setting mode, the bubble
  // result should not be remembered or affect the setting mode.
  if (signin_prefs.GetChromeSigninInterceptionUserChoice(gaia_id) ==
      ChromeSigninUserChoice::kAlwaysAsk) {
    return result;
  }

  SigninInterceptionResult processed_result = result;
  // Treat dismiss case: might turn into a decline if max dismiss count is
  // reached.
  if (processed_result == SigninInterceptionResult::kDismissed) {
    size_t dismiss_count =
        signin_prefs.IncrementChromeSigninInterceptionDismissCount(gaia_id);
    if (dismiss_count >= kMaxChromeSigninInterceptionDismissCount) {
      // Proceed with the result treated as declined since we reached the max
      // dismissal count, or the user is in the always ask mode.
      // TODO(crbug.com/319396084): Should we record something here?
      processed_result = SigninInterceptionResult::kDeclined;
    } else {
      // Max dismiss count not reached yet, proceed with a simple dismiss.
      return result;
    }
  }

  if (processed_result == SigninInterceptionResult::kAccepted) {
    // Save user choice as always sign in.
    signin_prefs.SetChromeSigninInterceptionUserChoice(
        gaia_id, ChromeSigninUserChoice::kSignin);
  }

  if (processed_result == SigninInterceptionResult::kDeclined) {
    // If the user had no explicit interaction with the bubble or the Chrome
    // Signin setting previously, then set it's first decline time.
    if (signin_prefs.GetChromeSigninInterceptionUserChoice(gaia_id) ==
        ChromeSigninUserChoice::kNoChoice) {
      signin_prefs.SetChromeSigninInterceptionLastBubbleDeclineTime(
          gaia_id, base::Time::Now());
    }

    MaybeUpdateRepromptInfoAfterDecline(signin_prefs, gaia_id);

    // Save user choice as do not sign in automatically.
    signin_prefs.SetChromeSigninInterceptionUserChoice(
        gaia_id, ChromeSigninUserChoice::kDoNotSignin);
  }

  return processed_result;
}

void DiceWebSigninInterceptor::OnChromeSigninChoice(
    const AccountInfo& account_info,
    SigninInterceptionResult result) {
  SigninInterceptionResult processed_result =
      ProcessChromeSigninUserChoice(result, account_info.gaia);

  switch (processed_result) {
    case SigninInterceptionResult::kIgnored:
      // Can happen if the browser is closed while the bubble is still opened.
    case SigninInterceptionResult::kNotDisplayed:
      // Can happen if the web contents is destroyed between the time the bubble
      // was requested to be displayed and actually being displayed.
    case SigninInterceptionResult::kDismissed:
      // Happens if the user closed the bubble without explicitly accepting or
      // declining.
      break;
    case SigninInterceptionResult::kDeclined:
      RecordChromeSigninNumberOfDismissesForAccount(account_info.gaia,
                                                    processed_result);
      break;
    case SigninInterceptionResult::kAcceptedWithExistingProfile:
      NOTREACHED()
          << "Those results are not expected within the Chrome Signin Bubble.";
    case SigninInterceptionResult::kAccepted:
      RecordChromeSigninNumberOfDismissesForAccount(account_info.gaia,
                                                    processed_result);

      auto access_point = signin_metrics::AccessPoint::
          ACCESS_POINT_CHROME_SIGNIN_INTERCEPT_BUBBLE;
      signin_metrics::LogSignInStarted(access_point);
      identity_manager_->GetPrimaryAccountMutator()->SetPrimaryAccount(
          account_info.account_id, signin::ConsentLevel::kSignin, access_point);
  }

  // In all cases we want to close the bubble after the choice is taken.
  Reset();
}

void DiceWebSigninInterceptor::OnProfileSwitchChoice(
    const std::string& email,
    const base::FilePath& profile_path,
    SigninInterceptionResult switch_profile) {
  if (switch_profile != SigninInterceptionResult::kAccepted) {
    Reset();
    return;
  }

  DCHECK(state_->interception_bubble_handle_);
  DCHECK(!state_->dice_signed_in_profile_creator_);
  // Unretained is fine because the profile creator is owned by this.
  state_->dice_signed_in_profile_creator_ =
      std::make_unique<DiceSignedInProfileCreator>(
          profile_, state_->account_id_, profile_path,
          base::BindOnce(&DiceWebSigninInterceptor::OnNewSignedInProfileCreated,
                         base::Unretained(this), std::nullopt));
}

void DiceWebSigninInterceptor::OnNewSignedInProfileCreated(
    std::optional<ProfilePresets> profile_presets,
    Profile* new_profile) {
  DCHECK(state_->dice_signed_in_profile_creator_);
  state_->dice_signed_in_profile_creator_.reset();

  if (!new_profile) {
    Reset();
    return;
  }

  // The profile presets are defined only when the profile has just been created
  // (with interception type kMultiUser or kEnterprise). If the profile is not
  // new (kProfileSwitch) or if it is a guest profile, then the presets are not
  // updated.
  bool is_new_profile = profile_presets.has_value();
  if (is_new_profile) {
    ProfileMetrics::LogProfileAddNewUser(
        ProfileMetrics::ADD_NEW_USER_SIGNIN_INTERCEPTION);
    // TODO(crbug.com/40775669): Remove the condition if Guest mode
    // option is removed.
    if (!new_profile->IsGuestSession()) {
      // Apply the new color to the profile.
      ThemeServiceFactory::GetForProfile(new_profile)
          ->SetUserColorAndBrowserColorVariant(
              profile_presets->profile_color,
              ui::mojom::BrowserColorVariant::kTonalSpot);

      // The new profile inherits the default search provider and the search
      // engine choice timestamp from the previous profile.
      SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
          *new_profile, profile_presets->search_engine_choice_data);
    }

    // TODO(crbug.com/40269992): Move this to DiceSignedInProfileCreator when
    // DisallowManagedProfileSignout is fully released.
    if (state_->intercepted_account_management_accepted_ &&
        base::FeatureList::IsEnabled(kDisallowManagedProfileSignout)) {
      auto* primary_account_mutator =
          IdentityManagerFactory::GetForProfile(new_profile)
              ->GetPrimaryAccountMutator();
      primary_account_mutator->SetPrimaryAccount(
          state_->account_id_, signin::ConsentLevel::kSignin,
          signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN);
    }

    // Set the ChromeSignin setting to always signin following accepting the
    // signin intercept and being signed in.
    if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
      CoreAccountInfo account_info =
          IdentityManagerFactory::GetForProfile(new_profile)
              ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
      if (!account_info.IsEmpty()) {
        SigninPrefs(*new_profile->GetPrefs())
            .SetChromeSigninInterceptionUserChoice(
                account_info.gaia, ChromeSigninUserChoice::kSignin);
      }
    }
  }

  enterprise_util::SetUserAcceptedAccountManagement(
      new_profile, state_->intercepted_account_management_accepted_);

  // Work is done in this profile, the flow continues in the
  // DiceWebSigninInterceptor that is attached to the new profile.
  // We pass relevant parameters from this instance to the new one.
  DiceWebSigninInterceptorFactory::GetForProfile(new_profile)
      ->CreateBrowserAfterSigninInterception(
          state_->account_id_, state_->web_contents_.get(),
          std::move(state_->interception_bubble_handle_), is_new_profile,
          *state_->interception_type_);
  Reset();
}

void DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult(
    const AccountInfo& account_info,
    SkColor profile_color,
    SigninInterceptionResult create) {
  signin_util::RecordEnterpriseProfileCreationUserChoice(
      /*enforced_by_policy=*/!signin_util::IsProfileSeparationEnforcedByProfile(
          profile_, account_info.email) &&
          !signin_util::IsProfileSeparationEnforcedByPolicies(
              state_->intercepted_account_profile_separation_policies_.value_or(
                  policy::ProfileSeparationPolicies())),
      /*created=*/create == SigninInterceptionResult::kAccepted);

  // Make sure existing account is a non-signed in profile.
  if (create == SigninInterceptionResult::kAccepted) {
    state_->intercepted_account_management_accepted_ = true;
    // In case of a reauth if there was no consent for management, do not create
    // a new profile.
    if (IsReauthPrimaryAccount(state_->new_account_interception_,
                               account_info.account_id, identity_manager_)) {
      enterprise_util::SetUserAcceptedAccountManagement(
          profile_, state_->intercepted_account_management_accepted_);
      Reset();
    } else {
      OnProfileCreationChoice(account_info, profile_color,
                              SigninInterceptionResult::kAccepted);
    }
  } else if (create == SigninInterceptionResult::kAcceptedWithExistingProfile) {
    state_->intercepted_account_management_accepted_ = true;
    if (GetPrimaryAccountInfo(identity_manager_).IsEmpty()) {
      identity_manager_->GetPrimaryAccountMutator()->SetPrimaryAccount(
          account_info.account_id, signin::ConsentLevel::kSignin,
          signin_metrics::AccessPoint::
              ACCESS_POINT_CHROME_SIGNIN_INTERCEPT_BUBBLE);
    } else {
      DCHECK_EQ(GetPrimaryAccountInfo(identity_manager_).account_id,
                account_info.account_id);
    }

    enterprise_util::SetUserAcceptedAccountManagement(
        profile_, state_->intercepted_account_management_accepted_);
    Reset();
  } else {
    DCHECK_EQ(SigninInterceptionResult::kDeclined, create)
        << "The user can only accept or decline";
    if (state_->interception_type_ ==
        WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced) {
      if (account_info == identity_manager_->GetPrimaryAccountInfo(
                              signin::ConsentLevel::kSignin)) {
        auto* primary_account_mutator =
            IdentityManagerFactory::GetForProfile(profile_)
                ->GetPrimaryAccountMutator();
        primary_account_mutator->ClearPrimaryAccount(
            signin_metrics::ProfileSignout::kAbortSignin);
      }

      auto* accounts_mutator = identity_manager_->GetAccountsMutator();
      accounts_mutator->RemoveAccount(
          account_info.account_id,
          signin_metrics::SourceForRefreshTokenOperation::
              kEnterpriseForcedProfileCreation_UserDecline);
    }
    OnProfileCreationChoice(account_info, profile_color,
                            SigninInterceptionResult::kDeclined);
  }
}

void DiceWebSigninInterceptor::OnNewBrowserCreated(bool is_new_profile) {
  DCHECK(state_->interception_bubble_handle_);
  state_->interception_bubble_handle_.reset();  // Close the bubble now.
  state_->session_startup_helper_.reset();

  // TODO(crbug.com/40775669): Remove |IsGuestSession| if Guest option is
  // no more supported.
  if (!is_new_profile || profile_->IsGuestSession()) {
    return;
  }

  Browser* browser = chrome::FindBrowserWithProfile(profile_);
  DCHECK(browser);
  delegate_->ShowFirstRunExperienceInNewProfile(browser, state_->account_id_,
                                                *state_->interception_type_);
}

// static
std::string DiceWebSigninInterceptor::GetPersistentEmailHash(
    const std::string& email) {
  int hash = base::PersistentHash(
                 gaia::CanonicalizeEmail(gaia::SanitizeEmail(email))) &
             0xFF;
  return base::StringPrintf("email_%i", hash);
}

size_t DiceWebSigninInterceptor::IncrementEmailToCountDictionaryPref(
    const char* pref_name,
    const std::string& email) {
  // TODO(b/314079566): Consider merging the different similar pref counts into
  // a single pref where the email hash maps to multiple values.
  ScopedDictPrefUpdate update(profile_->GetPrefs(), pref_name);
  std::string key = GetPersistentEmailHash(email);
  std::optional<int> count = update->FindInt(key);

  int value = count.value_or(0) + 1;
  update->Set(key, value);

  CHECK_GE(value, 0);
  return value;
}

void DiceWebSigninInterceptor::RecordChromeSigninNumberOfDismissesForAccount(
    const std::string& gaia_id,
    SigninInterceptionResult result) {
  CHECK(switches::IsExplicitBrowserSigninUIOnDesktopEnabled());
  CHECK(result == SigninInterceptionResult::kAccepted ||
        result == SigninInterceptionResult::kDeclined)
      << "Recording results only for accepting/declining the bubble. "
         "Result: "
      << static_cast<int>(result);

  std::string_view action_string =
      result == SigninInterceptionResult::kAccepted ? "Accept" : "Decline";
  base::UmaHistogramCounts100(
      base::StrCat(
          {"Signin.Intercept.ChromeSignin.DismissesBefore", action_string}),
      SigninPrefs(*profile_->GetPrefs())
          .GetChromeSigninInterceptionDismissCount(gaia_id));
}

bool DiceWebSigninInterceptor::HasUserDeclinedProfileCreation(
    const std::string& email) const {
  const base::Value::Dict& pref_data = profile_->GetPrefs()->GetDict(
      prefs::kProfileCreationInterceptionDeclined);
  std::optional<int> declined_count =
      pref_data.FindInt(GetPersistentEmailHash(email));
  // Check if the user declined 2 times.
  constexpr int kMaxProfileCreationDeclinedCount = 2;
  return declined_count &&
         declined_count.value() >= kMaxProfileCreationDeclinedCount;
}

void DiceWebSigninInterceptor::
    EnsureAccountLevelSigninRestrictionFetchInProgress(
        const AccountInfo& account_info,
        base::OnceCallback<void(const policy::ProfileSeparationPolicies&)>
            callback) {
  if (state_->account_level_signin_restriction_policy_fetcher_ != nullptr) {
    // A fetch is already in progress, don't start a new one.
    DCHECK_EQ(account_info.account_id, state_->account_id_);
    return;
  }

  if (intercepted_account_profile_separation_policies_response_for_testing_
          .has_value()) {
    std::move(callback).Run(
        intercepted_account_profile_separation_policies_response_for_testing_
            .value());
    return;
  }

  DCHECK(!state_->interception_info_available_timeout_.IsCancelled());

  state_->account_level_signin_restriction_policy_fetcher_ =
      std::make_unique<policy::UserCloudSigninRestrictionPolicyFetcher>(
          g_browser_process->browser_policy_connector(),
          g_browser_process->system_network_context_manager()
              ->GetSharedURLLoaderFactory());
  state_->account_level_signin_restriction_policy_fetcher_
      ->GetManagedAccountsSigninRestriction(
          identity_manager_, account_info.account_id, std::move(callback),
          policy::utils::IsPolicyTestingEnabled(profile_->GetPrefs(),
                                                chrome::GetChannel())
              ? profile_->GetPrefs()
                    ->GetDefaultPrefValue(
                        prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage)
                    ->GetString()
              : std::string());
}

void DiceWebSigninInterceptor::
    OnAccountLevelManagedAccountsSigninRestrictionReceived(
        const AccountInfo& account_info,
        const policy::ProfileSeparationPolicies& profile_separation_policies) {
  state_->intercepted_account_profile_separation_policies_ =
      profile_separation_policies;
  ProcessInterceptionOrWait(account_info, /*timed_out=*/false);
}

void DiceWebSigninInterceptor::RecordSigninInterceptionHeuristicOutcome(
    SigninInterceptionHeuristicOutcome outcome) const {
  // Record the outcome.
  base::UmaHistogramEnumeration("Signin.Intercept.HeuristicOutcome", outcome);

  // Record the latency, except in the case where this is a duplicate request
  // for the same interception.
  DCHECK_NE(state_->interception_start_time_, base::TimeTicks());
  if (outcome ==
      SigninInterceptionHeuristicOutcome::kAbortInterceptInProgress) {
    // This is a special-case where we immediately abort the intercept request
    // without first updating interception_start_time_ (because the previous
    // request has not completed).
    // Record the histogram for this request with zero duration.
    base::UmaHistogramTimes("Signin.Intercept.HeuristicLatency",
                            base::Milliseconds(0));
  } else {
    base::UmaHistogramTimes(
        "Signin.Intercept.HeuristicLatency",
        base::TimeTicks::Now() - state_->interception_start_time_);
  }
}

bool DiceWebSigninInterceptor::IsFullExtendedAccountInfoAvailable(
    const AccountInfo& account_info) const {
  if (!IsRequiredExtendedAccountInfoAvailable(account_info)) {
    return false;
  }
  return account_info.capabilities.is_subject_to_parental_controls() !=
         signin::Tribool::kUnknown;
}

bool DiceWebSigninInterceptor::managed_profile_creation_required_by_policy()
    const {
  return state_->interception_type_ ==
         WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced;
}

AccountInfo DiceWebSigninInterceptor::intercepted_account_info() const {
  return identity_manager_->FindExtendedAccountInfoByAccountId(
      state_->account_id_);
}

// static
base::TimeDelta
DiceWebSigninInterceptor::GetTimeSinceLastChromeSigninDeclineForTesting(
    const SigninPrefs& signin_prefs,
    const std::string& gaia_id) {
  return GetTimeSinceLastChromeSigninDecline(signin_prefs, gaia_id);
}
